package ch.ethz.fcl.metrobuzz.render.interchange.inter;

import java.util.List;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.data.interchange.Interchange;
import ch.ethz.fcl.metrobuzz.data.ltt.node.LTTNode;
import ch.ethz.fcl.metrobuzz.data.ltt.node.LTTNodeInterchange;
import ch.ethz.fcl.metrobuzz.render.techniques.circos.Ribbon;
import ch.ethz.fcl.metrobuzz.render.techniques.circos.version5.CircosDiagram5;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.render.techniques.cubic_hermite.CubicHermite;
import ch.ethz.fcl.metrobuzz.render.techniques.projection.Projection;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

/**
 * render connections between interchanges
 * 
 * @author Zeng Wei
 * @email zeng@arch.ethz.ch
 */
public class InterInterchangeRender {
	private static final float PARLLEL_COS_THRESHOLD = 0.999999F;

	public static float supportRatio = 0.1f;

	/**
	 * 
	 * @param proj
	 * @param interchanges
	 * @param choice
	 *            0 for color, 1 for glyph
	 */
	public static void render(Projection proj,
			List<? extends Interchange> interchanges, int choice) {
		for (Interchange interchange : interchanges) {
			List<Interchange> connectedInterchange = interchange
					.getConnectedInterchanges();

			CircosDiagram5 cv = (CircosDiagram5) interchange.getCircos();

			Point2D centroid = cv.getCentroid();
			Vector2d[] initDirs = cv.getData().getDirections()
					.getInitialDirections();
			Vector2d[] dirs = cv.getData().getDirections().getRepelDirections();

			if (cv == null || dirs == null)
				continue;

			float localTotal = cv.getData().getOutgoing()[0]
					+ cv.getData().getIncoming()[0];

			for (int i = 1; i < dirs.length; i++) {
				int color = cv.getData().getColorIndex()[i];

				float outgoingAngle = cv.getData().getOutgoing()[i]
						/ (cv.getData().getTotalSum() - localTotal)
						* cv.getRenderAngle();
				float incomingAngle = cv.getData().getIncoming()[i]
						/ (cv.getData().getTotalSum() - localTotal)
						* cv.getRenderAngle();
				float arcElementAngle = outgoingAngle + incomingAngle;

				Vector2d outStartDir = MBTools.rotate(dirs[i],
						-arcElementAngle / 2);

				Vector2d outEndDir = MBTools.rotate(outStartDir, outgoingAngle);
				Vector2d outCenterDir = MBTools.rotate(outStartDir,
						outgoingAngle / 2);

				Point2D p0 = centroid.getPointAt(outStartDir,
						cv.getArcElementOutboundRadius());
				Point2D p1 = centroid.getPointAt(outEndDir,
						cv.getArcElementOutboundRadius());
				Point2D r1OutMid = centroid.getPointAt(outCenterDir,
						cv.getArcElementOutboundRadius());

				// find corresponding interchange of the specific arc element
				Interchange interchange2 = null;
				for (Interchange i2 : connectedInterchange) {
					Point2D i2Centroid = i2.getCircosCentroid();
					Vector2d interInterchangeDir = new Vector2d(
							i2Centroid.getX() - centroid.getX(),
							i2Centroid.getY() - centroid.getY());
					if (MBTools.cos(initDirs[i], interInterchangeDir) > PARLLEL_COS_THRESHOLD) {
						interchange2 = i2;
						break;
					}
				}
				// find corresponding arc element index in interchange 2
				int i2AEIndex = -1;
				if (interchange2 != null) {
					Vector2d[] r2InitDirs = interchange2.getCircos().getData()
							.getDirections().getInitialDirections();

					for (int j = 1; j < r2InitDirs.length; j++) {
						if (MBTools.cos(initDirs[i], r2InitDirs[j]) < -PARLLEL_COS_THRESHOLD) {
							i2AEIndex = j;
							break;
						}
					}
				}

				if (i2AEIndex != -1 && color != MBColorTable.LIGHTGREY) {
					MBColorTable.assignColor(color, 0.1f);

					CircosDiagram5 cv2 = (CircosDiagram5) interchange2
							.getCircos();
					Point2D i2Centroid = cv2.getCentroid();
					Vector2d[] dirs2 = cv2.getData().getDirections()
							.getRepelDirections();
					float width = arcElementAngle / 1.5f * cv.getRadius()
							* 1.2f;

					if (choice == 0) {
						Point2D i1OutMid = centroid.getPointAt(dirs[i],
								cv.getRadius() * 1.2f);

						Point2D i2OutMid = i2Centroid.getPointAt(
								dirs2[i2AEIndex], cv2.getRadius() * 1.2f);
						MBColorTable.assignColor(color, 0.1f);
						MBGL.render(i1OutMid, width, i2OutMid, width, true, false);
					} else if (choice == 1) {
						float localTotal2 = cv2.getData().getOutgoing()[0]
								+ cv2.getData().getIncoming()[0];
						float outgoingAngle2 = cv2.getData().getOutgoing()[i2AEIndex]
								/ (cv2.getData().getTotalSum() - localTotal2)
								* cv2.getRenderAngle();
						float incomingAngle2 = cv2.getData().getIncoming()[i2AEIndex]
								/ (cv2.getData().getTotalSum() - localTotal2)
								* cv2.getRenderAngle();
						float totalAngle2 = outgoingAngle2 + incomingAngle2;

						Vector2d inEnd2 = MBTools.rotate(dirs2[i2AEIndex],
								totalAngle2 / 2);
						Vector2d inStart2 = MBTools.rotate(inEnd2,
								-incomingAngle2);
						Vector2d inCenter2 = MBTools.rotate(inEnd2,
								-incomingAngle2 / 2);

						Point2D p2 = cv2.getCentroid().getPointAt(inEnd2,
								cv2.getArcElementOutboundRadius());
						Point2D p3 = cv2.getCentroid().getPointAt(inStart2,
								cv2.getArcElementOutboundRadius());
						Point2D r2InMid = cv2.getCentroid().getPointAt(
								inCenter2, cv2.getArcElementOutboundRadius());

						GL11.glLineWidth(2.0f);
						GL11.glColor3f(1, 0, 0);
						Point2D[] ctrlPt1 = { interchange2.getCircosCentroid(),
								p2, p0, interchange.getCircosCentroid() };
						Point2D[] spline1 = CubicHermite.getCatmullRomSpline(
								100, ctrlPt1);

						GL11.glColor3f(0, 0, 1);
						Point2D[] ctrlPt2 = { interchange2.getCircosCentroid(),
								p3, p1, interchange.getCircosCentroid() };
						Point2D[] spline2 = CubicHermite.getCatmullRomSpline(
								100, ctrlPt2);

						Point2D[] ctrlPtMid = {
								interchange2.getCircosCentroid(), r2InMid,
								r1OutMid, interchange.getCircosCentroid() };
						Point2D[] splineMid = CubicHermite.getCatmullRomSpline(
								100, ctrlPtMid);
						Ribbon.renderGlyph(proj, spline1, spline2, splineMid,
								null, null);
					} else if (choice == 2) {
						// if either nod has flow volume less than threshold
						if (interchange.getCircos().getRenderAngle() < Math.PI
								* supportRatio
								|| interchange2.getCircos().getRenderAngle() < Math.PI
										* supportRatio)
							continue;

						float ribbonWidth = p0.distance(p1);

						float localTotal2 = cv2.getData().getOutgoing()[0]
								+ cv2.getData().getIncoming()[0];
						float outgoingAngle2 = cv2.getData().getOutgoing()[i2AEIndex]
								/ (cv2.getData().getTotalSum() - localTotal2)
								* cv2.getRenderAngle();
						float incomingAngle2 = cv2.getData().getIncoming()[i2AEIndex]
								/ (cv2.getData().getTotalSum() - localTotal2)
								* cv2.getRenderAngle();
						float totalAngle2 = outgoingAngle2 + incomingAngle2;

						Vector2d inEnd2Dir = MBTools.rotate(dirs2[i2AEIndex],
								totalAngle2 / 2);
						Vector2d inCenter2Dir = MBTools.rotate(inEnd2Dir,
								-incomingAngle2 / 2);
						Vector2d inStart2Dir = MBTools.rotate(inEnd2Dir,
								-incomingAngle2);

						Point2D r2InP0 = cv2.getCentroid().getPointAt(
								inEnd2Dir, cv2.getArcElementOutboundRadius());
						Point2D r2InMid = cv2.getCentroid()
								.getPointAt(inCenter2Dir,
										cv2.getArcElementOutboundRadius());
						Point2D r2InP1 = cv2.getCentroid().getPointAt(
								inStart2Dir, cv2.getArcElementOutboundRadius());

						LTTNode n1 = ((LTTNodeInterchange) interchange)
								.getLTTNode(), n2 = ((LTTNodeInterchange) interchange2)
								.getLTTNode();
						Point2D c1 = null, c2 = null;

						int minNodeLevel = Math.min(n1.getLevel(),
								n2.getLevel());
						boolean up = (minNodeLevel % 2) == 0;

						if (n1.getLevel() == 0) {
							c1 = r1OutMid;

							c2 = interchange2.getCircosCentroid();

							Vector2d dir = c1.getDir(c2);

							double upDirAngle = -Math.PI / 2;
							if (!up)
								upDirAngle = Math.PI / 2;
							Vector2d verticalDir = MBTools.rotate(dir,
									upDirAngle);
							float length = c1.distance(c2);
							Point2D midPt = c1.getPointAt(c2, 0.5f);
							Point2D midCtrlPt = midPt.getPointAt(verticalDir,
									length / 2);

							Point2D midCtrlPtLow = midPt.getPointAt(
									verticalDir,
									length / 2 - (float) Math.sqrt(2) / 2
											* ribbonWidth);
							Point2D midCtrlPtUp = midPt.getPointAt(verticalDir,
									length / 2 + (float) Math.sqrt(2) / 2
											* ribbonWidth);

							Vector2d c1Mid = c1.getDir(midCtrlPt);
							Vector2d c1MidOpps = MBTools.rotate(c1Mid, Math.PI);
							Point2D endCtrlPtLow = c2.getPointAt(c1MidOpps,
									ribbonWidth / 2);
							Point2D endCtrlPtUp = c2.getPointAt(c1Mid,
									ribbonWidth / 2);

							Point2D[] ctrlPointsMid = new Point2D[] { c1,
									midCtrlPt, c2 };
							Point2D[] ctrlPointsLow = new Point2D[] { p1,
									midCtrlPtLow, endCtrlPtLow };
							Point2D[] ctrlPointsUp = new Point2D[] { p0,
									midCtrlPtUp, endCtrlPtUp };
							Ribbon.renderAsGlyph(proj, ctrlPointsUp,
									ctrlPointsLow, ctrlPointsMid, null, null);
						} else if (n2.getLevel() == 0) {
							c1 = interchange.getCircosCentroid();
							c2 = r2InMid;

							Vector2d dir = c1.getDir(c2);

							double upDirAngle = -Math.PI / 2;
							if (!up)
								upDirAngle = Math.PI / 2;
							Vector2d verticalDir = MBTools.rotate(dir,
									upDirAngle);
							float length = c1.distance(c2);
							Point2D midPt = c1.getPointAt(c2, 0.5f);
							Point2D midCtrlPt = midPt.getPointAt(verticalDir,
									length / 2);

							Point2D midCtrlPtUp = midPt.getPointAt(verticalDir,
									length / 2 - (float) Math.sqrt(2) / 2
											* ribbonWidth);
							Point2D midCtrlPtLow = midPt.getPointAt(
									verticalDir,
									length / 2 + (float) Math.sqrt(2) / 2
											* ribbonWidth);

							Vector2d midC2 = midCtrlPt.getDir(c2);
							Vector2d midC2Opps = MBTools.rotate(midC2, Math.PI);
							Point2D startCtrlPtLow = c1.getPointAt(midC2Opps,
									ribbonWidth / 2);
							Point2D startCtrlPtUp = c1.getPointAt(midC2,
									ribbonWidth / 2);

							Point2D[] ctrlPointsMid = new Point2D[] { c1,
									midCtrlPt, c2 };
							Point2D[] ctrlPointsUp = new Point2D[] {
									startCtrlPtUp, midCtrlPtUp, r2InP1 };
							Point2D[] ctrlPointsLow = new Point2D[] {
									startCtrlPtLow, midCtrlPtLow, r2InP0 };
							Ribbon.renderAsGlyph(proj, ctrlPointsUp,
									ctrlPointsLow, ctrlPointsMid, null, null);
						} else {
							c1 = interchange.getCircosCentroid();
							c2 = interchange2.getCircosCentroid();
							render(proj, c1, interchange.getCircosRadius(),
									null, c2, interchange2.getCircosRadius(),
									null, ribbonWidth, up);
						}
					}
				}

			}
		}
	}

	/**
	 * render directional inter interchanges with specific width and color,
	 * interchanges are ordered
	 * 
	 * @param proj
	 * @param interchanges
	 */
	public static void render(Projection proj,
			List<? extends Interchange> interchanges, float interWidth,
			float[] color) {
		for (int k = 0; k < interchanges.size() - 1; k++) {
			Interchange interchange = interchanges.get(k);
			Interchange nextInterchange = interchanges.get(k + 1);
			Vector2d interDir = interchange.getCircosCentroid().getDir(
					nextInterchange.getCircosCentroid());

			CircosDiagram5 cv = (CircosDiagram5) interchange.getCircos();

			Point2D centroid = cv.getCentroid();
			Vector2d[] initDirs = cv.getData().getDirections()
					.getInitialDirections();
			Vector2d[] dirs = cv.getData().getDirections().getRepelDirections();

			if (cv == null || initDirs == null || dirs == null)
				continue;

			float localTotal = cv.getData().getOutgoing()[0]
					+ cv.getData().getIncoming()[0];

			for (int i = 1; i < dirs.length; i++) {
				// if this is not the specified direction
				if (MBTools.cos(initDirs[i], interDir) < PARLLEL_COS_THRESHOLD)
					continue;

				float outgoingAngle = cv.getData().getOutgoing()[i]
						/ (cv.getData().getTotalSum() - localTotal)
						* cv.getRenderAngle();
				float incomingAngle = cv.getData().getIncoming()[i]
						/ (cv.getData().getTotalSum() - localTotal)
						* cv.getRenderAngle();
				float arcElementAngle = outgoingAngle + incomingAngle;

				Vector2d outStartDir = MBTools.rotate(dirs[i],
						-arcElementAngle / 2);
				Vector2d outCenterDir = MBTools.rotate(outStartDir,
						outgoingAngle / 2);

				Point2D r1OutMid = centroid.getPointAt(outCenterDir,
						cv.getArcElementOutboundRadius());

				Interchange interchange2 = nextInterchange;
				// find corresponding arc element index in interchange 2
				int i2AEIndex = -1;
				if (interchange2 != null) {
					Vector2d[] r2InitDirs = interchange2.getCircos().getData()
							.getDirections().getInitialDirections();

					for (int j = 1; j < r2InitDirs.length; j++) {
						if (MBTools.cos(initDirs[i], r2InitDirs[j]) < -PARLLEL_COS_THRESHOLD) {
							i2AEIndex = j;
							break;
						}
					}
				}

				if (i2AEIndex != -1) {
					CircosDiagram5 cv2 = (CircosDiagram5) interchange2
							.getCircos();
					Vector2d[] dirs2 = cv2.getData().getDirections()
							.getRepelDirections();

					float localTotal2 = cv2.getData().getOutgoing()[0]
							+ cv2.getData().getIncoming()[0];
					float outgoingAngle2 = cv2.getData().getOutgoing()[i2AEIndex]
							/ (cv2.getData().getTotalSum() - localTotal2)
							* cv2.getRenderAngle();
					float incomingAngle2 = cv2.getData().getIncoming()[i2AEIndex]
							/ (cv2.getData().getTotalSum() - localTotal2)
							* cv2.getRenderAngle();
					float totalAngle2 = outgoingAngle2 + incomingAngle2;

					Vector2d inEnd2Dir = MBTools.rotate(dirs2[i2AEIndex],
							totalAngle2 / 2);
					Vector2d inCenter2Dir = MBTools.rotate(inEnd2Dir,
							-incomingAngle2 / 2);

					Point2D r2InMid = cv2.getCentroid().getPointAt(
							inCenter2Dir, cv2.getArcElementOutboundRadius());

					LTTNode n1 = ((LTTNodeInterchange) interchange)
							.getLTTNode(), n2 = ((LTTNodeInterchange) interchange2)
							.getLTTNode();

					Point2D c1 = null, c2 = null;

					int minNodeLevel = Math.min(n1.getLevel(), n2.getLevel());
					boolean up = (minNodeLevel % 2) == 0;

					if (n1.getLevel() == 0) {
						c1 = r1OutMid;
					} else
						c1 = interchange.getCircosCentroid();

					if (n2.getLevel() == 0)
						c2 = r2InMid;
					else
						c2 = interchange2.getCircosCentroid();

					render(proj, c1, interchange.getCircosRadius(), color, c2,
							interchange2.getCircosRadius(), color, interWidth,
							up);
				}
			}

		}
	}

	public static void render(Projection proj, Point2D p1, float radius1,
			float[] color1, Point2D p2, float radius2, float[] color2,
			float width, boolean up) {
		Vector2d dir = new Vector2d(p2.getX() - p1.getX(), p2.getY()
				- p1.getY());
		double upDirAngle = -Math.PI / 2;
		if (!up)
			upDirAngle = Math.PI / 2;
		Vector2d verticalDir = MBTools.rotate(dir, upDirAngle);
		float length = p1.distance(p2);
		Point2D midPt = p1.getPointAt(p2, 0.5f);
		Point2D midCtrlPt = midPt.getPointAt(verticalDir, length / 2);
		Point2D[] ctrlPoints = new Point2D[] { p1, midCtrlPt, p2 };

		MBGL.enableBlend();

		Ribbon.renderAsGlyph(proj, ctrlPoints, width, width, color1, color2);
	}
}
